package com.yindian.cos.easy.security.filter;

import com.yindian.cos.easy.security.authentication.AbstractAuthentication;
import com.yindian.cos.easy.security.authentication.AuthenticationChain;
import com.yindian.cos.easy.security.authentication.login.UserDetail;
import com.yindian.cos.easy.security.authentication.login.normal.AbstractLoginAuthentication;
import com.yindian.cos.easy.security.authentication.login.social.core.AbstractSocialAuthentication;
import com.yindian.cos.easy.security.authentication.logout.AbstractLogoutAuthentication;
import com.yindian.cos.easy.security.authentication.verify.AbstractVerifyAuthentication;
import com.yindian.cos.easy.security.authentication.verify.SystemPathVerifyAuthentication;
import com.yindian.cos.easy.security.context.AuthenticationContext;
import com.yindian.cos.easy.security.handler.AuthenticationFailureHandler;
import com.yindian.cos.easy.security.handler.AuthenticationSuccessHandler;
import com.yindian.cos.easy.security.handler.LoginSuccessHandler;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * 核心的拦截链执行逻辑
 *
 * @author zhangchuanhao
 * @create 2018/11/22
 */
public class DispatchFilterHelper {

    private final AuthenticationChain authenticationChain;

    private final AuthenticationFailureHandler authenticationFailureHandler;

    private final LoginSuccessHandler loginSuccessHandler;

    private final AuthenticationSuccessHandler authenticationSuccessHandler;

    public DispatchFilterHelper(AuthenticationChain authenticationChain,
                                AuthenticationFailureHandler authenticationFailureHandler,
                                LoginSuccessHandler loginSuccessHandler,
                                AuthenticationSuccessHandler authenticationSuccessHandler) {
        this.authenticationChain = authenticationChain;
        this.authenticationFailureHandler = authenticationFailureHandler;
        this.loginSuccessHandler = loginSuccessHandler;
        this.authenticationSuccessHandler = authenticationSuccessHandler;
    }

    /**
     * 执行核心拦截逻辑
     *
     * @param request
     * @param response
     * @param filterChain
     * @throws IOException
     */
    public void dispatch(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        AuthenticationContext authenticationContext = AuthenticationContext.getCurrentContext();
        authenticationContext.setRequest(request);
        authenticationContext.setResponse(response);
        authenticationContext.setFilterChain(filterChain);
        List<AbstractAuthentication> abstractAuthenticationList = authenticationChain.getAuthentications();
        for (AbstractAuthentication abstractAuthentication : abstractAuthenticationList) {
            try {
                if (abstractAuthentication.support(request)) {
                    authenticationContext.setAuthentication(abstractAuthentication.getClass().getName());
                    boolean isContinue = abstractAuthenticationAdapter(request, response, abstractAuthentication, filterChain);
                    if (!isContinue) {
                        authenticationContext.clear();
                        break;
                    }
                }
            } catch (Exception ex) {
                if (authenticationFailureHandler != null) {
                    authenticationFailureHandler.handler(request, response, ex);
                    authenticationContext.clear();
                    break;
                } else {
                    authenticationContext.clear();
                    throw ex;
                }
            }
        }
    }

    /**
     * AbstractAuthentication适配
     *
     * @param request
     * @param response
     * @param abstractAuthentication
     * @return
     * @throws IOException
     */
    public boolean abstractAuthenticationAdapter(HttpServletRequest request,
                                                 HttpServletResponse response,
                                                 AbstractAuthentication abstractAuthentication,
                                                 FilterChain filterChain) throws IOException, ServletException {

        /**
         * 如果是表单、短信登录则调用UserService后直接进入LoginSuccessHandler，并且执行完后不会继续向下执行
         */
        if (abstractAuthentication instanceof AbstractLoginAuthentication) {
            AbstractLoginAuthentication abstractLoginAuthentication = (AbstractLoginAuthentication) abstractAuthentication;
            return executeAbstractAuthentication(request, response, abstractLoginAuthentication);
        }
        /**
         * 如果是社交登录：
         *  1、如果用户第一次登录则会引导跳转至配置的自定的注册页面
         *  2、如果不是第一次登录则调用SocialUserService后直接进入LoginSuccessHandler
         */
        else if (abstractAuthentication instanceof AbstractSocialAuthentication) {
            AbstractSocialAuthentication abstractSocialAuthentication = (AbstractSocialAuthentication) abstractAuthentication;
            return executeAbstractAuthentication(response, abstractSocialAuthentication);
        }
        /**
         * 自定义的拦截规则：
         * 1、如果next()为true会继续向下执行，否则会抛出相应的异常，进入AuthenticationFailureHandler
         * 2、如果next()为false会结束执行，调用AuthenticationSuccessHandler
         */
        else if (abstractAuthentication instanceof AbstractVerifyAuthentication) {
            AbstractVerifyAuthentication abstractVerifyAuthentication = (AbstractVerifyAuthentication) abstractAuthentication;
            return executeAbstractAuthentication(request, response, abstractVerifyAuthentication, filterChain);
        }
        /**
         * 退出拦截器
         */
        else if (abstractAuthentication instanceof AbstractLogoutAuthentication) {
            AbstractLogoutAuthentication abstractVerifyAuthentication = (AbstractLogoutAuthentication) abstractAuthentication;
            return executeAbstractAuthentication(request, response, abstractVerifyAuthentication, filterChain);
        }
        return false;
    }

    /**
     * 表单登录和短信登录拦截方法执行
     *
     * @param request
     * @param response
     * @param abstractLoginAuthentication
     * @return
     * @throws IOException
     */
    public boolean executeAbstractAuthentication(HttpServletRequest request,
                                                 HttpServletResponse response,
                                                 AbstractLoginAuthentication abstractLoginAuthentication) throws IOException {
        UserDetail userDetail = abstractLoginAuthentication.execute(request);
        if (loginSuccessHandler != null) {
            loginSuccessHandler.handler(request, response, userDetail);
        }
        return false;
    }

    /**
     * 第三方登录拦截方法执行
     *
     * @param response
     * @param abstractSocialAuthentication
     * @return
     * @throws IOException
     */
    public boolean executeAbstractAuthentication(HttpServletResponse response,
                                                 AbstractSocialAuthentication abstractSocialAuthentication) throws IOException {
        abstractSocialAuthentication.redirectAuthorization(response);
        return false;
    }

    /**
     * 自定义拦截器的方法执行
     *
     * @param request
     * @param response
     * @param abstractVerifyAuthentication
     * @param filterChain
     * @return
     * @throws IOException
     */
    public boolean executeAbstractAuthentication(HttpServletRequest request,
                                                 HttpServletResponse response,
                                                 AbstractVerifyAuthentication abstractVerifyAuthentication,
                                                 FilterChain filterChain) throws IOException, ServletException {
        abstractVerifyAuthentication.verify(request, response);
        if (abstractVerifyAuthentication.next(request)) {
            return true;
        }
        if (abstractVerifyAuthentication instanceof SystemPathVerifyAuthentication) {
            filterChain.doFilter(request, response);
        } else {
            if (authenticationSuccessHandler != null) {
                authenticationSuccessHandler.handler(request, response, filterChain);
            }
        }
        return false;
    }

    /**
     * 退出拦截器的方法执行
     *
     * @param request
     * @param response
     * @param abstractSocialAuthentication
     * @return
     * @throws IOException
     */
    public boolean executeAbstractAuthentication(HttpServletRequest request,
                                                 HttpServletResponse response,
                                                 AbstractLogoutAuthentication abstractSocialAuthentication,
                                                 FilterChain filterChain) throws IOException {
        abstractSocialAuthentication.logout(request, response, filterChain);
        return false;
    }

}
